/* Copyright (c) 2004-2008, Andriy Nikolov
 * All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.processmining.email;

import java.util.*;
import java.io.*;

/**
 * <p>Describes one instance of the process log task within a process case.
 *
 * @author Andriy Nikolov
 * @version 1.0
 */
public class ProcessTask {

  Vector messageList;


  ProcessCase relatedToCase;
  Vector eventList;

  ProcessTaskType type;
  Date startTime;
  Date endTime;
  Vector participants;

  public ProcessTask() {
    jbInit();
  }

  /**
   * Creates a ProcessTask instance with a given name of the task type and a
   * reference to a given process case.
   *
   * @param name String
   * @param relatedCase ProcessCase
   */
  public ProcessTask(String name, ProcessCase relatedCase) {
    jbInit();
//    this.name = name;
    type = relatedCase.getProcessLog().getTaskTypeByName(name);
    relatedToCase = relatedCase;
  }

  /**
   * Creates a ProcessTask instance with a given task type and a reference to a
   * given process case.
   *
   * @param type ProcessTaskType
   * @param relatedCase ProcessCase
   */
  public ProcessTask(ProcessTaskType type, ProcessCase relatedCase) {
    jbInit();
    this.type = type;
    this.relatedToCase = relatedCase;
  }

  private void jbInit() {
    messageList = new Vector();
    participants = new Vector();
    eventList = new Vector();

  }

  /**
   * Gets the list of e-mail messages related to the processing of the task.
   *
   * @return Vector
   */
  public Vector getMessageList() {
    return messageList;
  }

  public void addMessage(EMailMessage mes) {
    messageList.add(mes);
  }

  /**
   * Gets the name of the task type.
   *
   * @return String
   */
  public String getName() {
    try {
      return type.getName();
    } catch (Exception e) {
      return null;
    }
  }

  /**
   * Gets the task type.
   *
   * @return ProcessTaskType
   */
  public ProcessTaskType getType() {
    return type;
  }

  /**
   * Sets the task type.
   *
   * @param type ProcessTaskType
   */
  public void setType(ProcessTaskType type) {
    this.type = type;
  }

//  public void setName(String name) {
//    this.name = name;
//  }

  public ProcessCase getCase() {
    return relatedToCase;
  }

  public void setCase(ProcessCase relatedCase) {
    relatedToCase = relatedCase;
  }

  public void save(PrintWriter writer) {
  }

  /**
   * Creates events for the task.
   */
  public void build() {
    createEvents();
  }

  private void cleanMessages() {
    EMailMessage iterMessage;
    Hashtable messageTable = new Hashtable();
    for(int i=0;i<messageList.size();i++) {
      iterMessage = (EMailMessage)messageList.get(i);
      if(!messageTable.containsKey(iterMessage.getSubject())) {
        messageTable.put(iterMessage.getSubject(), iterMessage);
      }
    }
    messageList.clear();
    Enumeration messageEnumerator = messageTable.elements();
    while(messageEnumerator.hasMoreElements()) {
      iterMessage = (EMailMessage)messageEnumerator.nextElement();
      messageList.add(iterMessage);
    }
    Collections.sort(messageList, new TimestampOwnerComparator());
  }

  private void createEvents() {
    EMailMessage iterMessage;
    ProcessEventType curType, type;
    ProcessEvent event;

    if((relatedToCase.getProcessLog().isIgnoreReplies())&&(relatedToCase.getProcessLog().getEventSourceType()==ProcessLog.EVENT_ONETYPE)) {
      cleanMessages();
    }


    for(int i=0;i<messageList.size();i++) {
      iterMessage = (EMailMessage)messageList.get(i);
      type = null;

      switch(relatedToCase.getProcessLog().getEventSourceType()) {
        case ProcessLog.EVENT_SUBJECT:
          for (int j=0;j<relatedToCase.getProcessLog().getEventTypesList().size();j++) {
            curType = (ProcessEventType)relatedToCase.getProcessLog().getEventTypesList().get(j);
            if((iterMessage.getSubject().toLowerCase().indexOf(curType.getAliasName().toLowerCase())!=-1)&&(!curType.getAliasName().equals(""))) {
              type = curType;
              break;
            }
          }
          break;
        case ProcessLog.EVENT_ONETYPE:
          if(iterMessage.getSubject().toLowerCase().startsWith("re:"))
            continue;
          type = (ProcessEventType)relatedToCase.getProcessLog().getEventTypesTable().get("normal");
          break;
        default:
          type = (ProcessEventType)relatedToCase.getProcessLog().getEventTypesTable().get(iterMessage.getPhase());
          break;
      }
      if(type!=null) {
        event = new ProcessEvent(this, iterMessage);
        event.setType(type);
        event.setTime(iterMessage.getSentOn());
        if(relatedToCase.getProcessLog().getCaseSourceType()!=ProcessLog.CASE_SENDER) {
          event.setOriginator(iterMessage.getFrom().getName());
        }
        relatedToCase.addEvent(event);
        eventList.add(event);
      }
    }
    if (relatedToCase.getProcessLog().hasToAssignMissingEvents()) {
      insertMissingEvents();
    }
  }

  private void insertMissingEvents() {
    Vector standardEvents = new Vector();
    ProcessEvent e1, e2;
    ProcessEventType et1, et2;
    Calendar calendar = new GregorianCalendar();
    int i;
    for (i=0;i<eventList.size();i++) {
      e1 = (ProcessEvent)eventList.get(i);
      if(!e1.getType().getStandardName().equals("unknown")) {
        standardEvents.add(e1);
      }
    }
    //fill all gaps
    if(standardEvents.size()>=2) {
      for (i=0; i < standardEvents.size()-1; i++) {
        e1 = (ProcessEvent)standardEvents.get(i);
        e2 = (ProcessEvent)standardEvents.get(i+1);
        while((!e1.getType().directlyPrecedes(e2.getType()))&&(!e1.getType().isFinal())) {
          et1 = (ProcessEventType)relatedToCase.getProcessLog().getEventTypesTable().get(e1.getType().getNextTypeName());
          calendar.setTime(e1.getTime());
          e1 = new ProcessEvent(this);
          e1.setType(et1);
          calendar.add(Calendar.MINUTE, 1);
          e1.setTime(calendar.getTime());
          eventList.add(e1);
          relatedToCase.addEvent(e1);
        }
        while(!e2.getType().directlySucceeds(e1.getType())&&(!e2.getType().isStart())) {
          et2 = (ProcessEventType)relatedToCase.getProcessLog().getEventTypesTable().get(e2.getType().getPreviousTypeName());
          calendar.setTime(e2.getTime());
          e2 = new ProcessEvent(this);
          e2.setType(et2);
          calendar.add(Calendar.MINUTE, -1);
          e2.setTime(calendar.getTime());
          eventList.add(e2);
          relatedToCase.addEvent(e2);
        }
      }
    }
    // insert before and after
    if(!standardEvents.isEmpty()) {
      e1 = (ProcessEvent)standardEvents.firstElement();
      while(!e1.getType().isStart()) {
        et1 = (ProcessEventType)relatedToCase.getProcessLog().getEventTypesTable().get(e1.getType().getPreviousTypeName());
        calendar.setTime(e1.getTime());
        e1 = new ProcessEvent(this);
        e1.setType(et1);
        calendar.add(Calendar.MINUTE, -1);
        e1.setTime(calendar.getTime());
        eventList.add(e1);
        relatedToCase.addEvent(e1);
      }
      e1 = (ProcessEvent)standardEvents.lastElement();
      while(!e1.getType().isFinal()) {
        et1 = (ProcessEventType)relatedToCase.getProcessLog().getEventTypesTable().get(e1.getType().getNextTypeName());
        calendar.setTime(e1.getTime());
        e1 = new ProcessEvent(this);
        e1.setType(et1);
        calendar.add(Calendar.MINUTE, -1);
        e1.setTime(calendar.getTime());
        eventList.add(e1);
        relatedToCase.addEvent(e1);
      }
    }
  }
}
